﻿using System;
using System.Collections.Generic;
using FinanSoft.DataLayer;
using FinanSoft.Entities;
using FinanSoft.Utilities;

namespace FinanSoft.BusinessLayer
{
    class ReportManager
    {
        #region Attributes
        private bool _isError;
        private String _errorMessage;
        private Company _myCompany;
        #endregion

        #region Properties
        public bool IsError
        {
            get
            {
                bool tempError = _isError;
                _isError = false;
                return tempError;
            }
        }

        public String ErrorMessage
        {
            get
            {
                String tempError = _errorMessage;
                _isError = false;
                _errorMessage = "";
                return tempError;
            }
        }

        public Company LoadedCompany
        {
            get
            {
                return ReportDataManager.GetInstance().GetCompanyInfo(); ;
            }
        }
        #endregion

        #region Singleton

        private ReportManager()
        {
            _myCompany = ReportDataManager.GetInstance().GetCompanyInfo();
        }

        /// <summary>
        /// Única instancia de esta clase
        /// </summary>
        private static ReportManager _instance;

        /// <summary>
        /// Método para implementar el patrón de Singleton
        /// </summary>
        /// <returns>Instancia única de la clase</returns>
        public static ReportManager GetInstance()
        {
            if (_instance == null)
                _instance = new ReportManager();
            return _instance;
        }

        #endregion

        #region Public Methods

        public bool CreateCompany(Company company, User administrator, String password)
        {
            bool result = ReportDataManager.GetInstance().CreateCompany(company, administrator, password);
            if (!result)
            {
                _isError = true;
                _errorMessage = Constants.ERROR_INVALID_DATABASE_PROVIDED;
            }
            return result;
        }

        /// <summary>
        /// Obtiene las cuentas pertenecientes al balance de comprobación de un periodo para la moneda proporcionada.
        /// </summary>
        /// <param name="periodId">Id del periodo para el que se quiere el reporte</param>
        /// <param name="currencyId">Id de la moneda para la que se quiere el reporte. Solo local o del sistema</param>
        /// <returns>Cuentas del reporte. Cada una es un árbol.</returns>
        public Account[] GetPeriodTrialBalance(int periodId, int currencyId)
        {
            return buildReport(ReportDataManager.GetInstance().GetPeriodAccountBalances(periodId, currencyId), currencyId);
        }

        /// <summary>
        /// Obtiene las cuentas pertenecientes al balance de comprobación de una temporada para la moneda proporcionada.
        /// </summary>
        /// <param name="seasonId">Id de la temporada para la que se quiere el reporte</param>
        /// <param name="currencyId">Id de la moneda para la que se quiere el reporte. Solo local o del sistema</param>
        /// <returns>Cuentas del reporte. Cada una es un árbol.</returns>
        public Account[] GetSeasonTrialBalance(int seasonId, int currencyId)
        {
            return buildReport(ReportDataManager.GetInstance().GetSeasonAccountBalances(seasonId, currencyId), currencyId);
        }

        /// <summary>
        /// Obtiene las cuentas pertenecientes al estado de resultados de un periodo para la moneda proporcionada.
        /// </summary>
        /// <param name="periodId">Id del periodo para el que se quiere el reporte</param>
        /// <param name="currencyId">Id de la moneda para la que se quiere el reporte. Solo local o del sistema</param>
        /// <returns>Cuentas del reporte. Cada una es un árbol.</returns>
        public Account[] GetPeriodIncomeStatement(int periodId, int currencyId, out List<Account> specialAccounts)
        {
            return filterIncomeStatement(ReportDataManager.GetInstance().GetPeriodAccountBalances(periodId, currencyId), currencyId, out specialAccounts);
        }

        /// <summary>
        /// Obtiene las cuentas pertenecientes al estado de resultados de una temporada para la moneda proporcionada.
        /// </summary>
        /// <param name="seasonId">Id de la temporada para la que se quiere el reporte</param>
        /// <param name="currencyId">Id de la moneda para la que se quiere el reporte. Solo local o del sistema</param>
        /// <returns>Cuentas del reporte. Cada una es un árbol.</returns>
        public Account[] GetSeasonIncomeStatement(int seasonId, int currencyId, out List<Account> specialAccounts)
        {
            return filterIncomeStatement(ReportDataManager.GetInstance().GetSeasonAccountBalances(seasonId, currencyId), currencyId, out specialAccounts);
        }

        /// <summary>
        /// Obtiene las cuentas pertenecientes al balance general de un periodo para la moneda proporcionada.
        /// </summary>
        /// <param name="periodId">Id del periodo para el que se quiere el reporte</param>
        /// <param name="currencyId">Id de la moneda para la que se quiere el reporte. Solo local o del sistema</param>
        /// <returns>Cuentas del reporte. Cada una es un árbol.</returns>
        public Account[] GetPeriodBalanceSheet(int periodId, int currencyId)
        {
            return filterBalanceSheet(ReportDataManager.GetInstance().GetPeriodAccountBalances(periodId, currencyId), currencyId);
        }

        /// <summary>
        /// Obtiene las cuentas pertenecientes al balance general de una temporada para la moneda proporcionada.
        /// </summary>
        /// <param name="seasonId">Id de la temporada para la que se quiere el reporte</param>
        /// <param name="currencyId">Id de la moneda para la que se quiere el reporte. Solo local o del sistema</param>
        /// <returns>Cuentas del reporte. Cada una es un árbol.</returns>
        public Account[] GetSeasonBalanceSheet(int seasonId, int currencyId)
        {
            return filterBalanceSheet(ReportDataManager.GetInstance().GetSeasonAccountBalances(seasonId, currencyId), currencyId);
        }



        /// <summary>
        /// Modifica la compañía  con la cédula jurídica brindada
        /// </summary>
        /// <param name="id_legal">Cédula jurídica</param>
        /// <param name="nombre">Nombre de la empresa</param>
        /// <param name="telefono">Teléfono</param>
        /// <param name="fax">Fax</param>
        /// <param name="dividendPercentage">Número entre 0 y 100 inclusive para calcular el gasto de dividendos en el cierre contable</param>
        /// <param name="taxPercentage">Número entre 0 y 100 inclusive para calcular el gasto de impuesto de renta en el cierre contable</param>
        /// <returns></returns>
        public bool ModifyCompany(String id_legal, String nombre, String telefono, String fax, double dividendPercentage, double taxPercentage)
        {
            Byte[] logo = _myCompany.Logo;
            Company ModCompany = new Company(nombre, id_legal, telefono, fax, logo, dividendPercentage, taxPercentage);

            bool noErrorDB = ReportDataManager.GetInstance().ModifyCompany(ModCompany);
            if (!noErrorDB)
            {
                _isError = true;
                _errorMessage = Constants.ERROR_MOD_COMPANY_FAILED;
                return false;
            }
            else
                 _myCompany = ModCompany;
            return true;
        }


        #endregion

        #region Private/Protected Methods

        /// <summary>
        /// Obtiene una copia de una cuenta, sin contar sus saldos ni sus hijos
        /// </summary>
        /// <param name="acc">Cuenta a copiar</param>
        /// <returns>Copia de la cuenta(nuevo objeto)</returns>
        private Account copyAccount(Account acc)
        {
            Account copy = new Account();
            copy.AccountCode = acc.AccountCode;
            copy.AccountCurrency = acc.AccountCurrency;
            copy.AccountID = acc.AccountID;
            copy.AccountType = acc.AccountType;
            copy.ForeignName = acc.ForeignName;
            copy.IsTitle = acc.IsTitle;
            if(copy.IsTitle)
                copy.Children = new Dictionary<int, Account>();
            copy.Name = acc.Name;
            copy.SpecialType = acc.SpecialType;
            if(acc.Parent != null)
                copy.Parent = copyAccount(acc.Parent);
            return copy;
        }

        /// <summary>
        /// Construye el árbol de cuentas de abajo hacia arriba. Parte de la cuenta Child y agrega a la lista
        /// su padre máximo (la cuenta que no tiene padres). A su vez, ingresa a sus padres y así mismo dentro del
        /// árbol de su padre máximo
        /// </summary>
        /// <param name="list">Lista de padres máximos</param>
        /// <param name="child">Cuenta para la que se construye el árbol</param>
        private void addParents(Dictionary<int, Account> list, Account child)
        {
            if (child.Parent == null)
                list.Add(child.AccountID, child);
            else
            {
                foreach (Account acc in list.Values)
                {
                    Account possibleParent = acc.GetAccount(child.Parent.AccountID);
                    if (possibleParent != null)
                    {
                        possibleParent.AddAccount(child);
                        return;
                    }
                }
                Account parent = copyAccount(child.Parent);
                parent.AddAccount(child);
                addParents(list, parent);
            }
        }

        /// <summary>
        /// Construye un reporte con base en los balances que recibe de parámetro. Es especial para el estado de resultados puesto
        /// que también devuelve la lista de cuentas especiales que necesita (utilidades retenidas, impuestos, dividendos, etc.)
        /// </summary>
        /// <param name="reports">Balances de las cuentas que pertenecen al reporte</param>
        /// <param name="currencyId">Moneda en que se está construyendo el reporte</param>
        /// <param name="specialAccounts">Cuentas especiales del reporte</param>
        /// <returns>Lista de cuentas que conforman el reporte</returns>
        private Account[] buildIncomeReport(List<AccountBalanceReport> reports, int currencyId, out List<Account> specialAccounts)
        {
            CatalogManager catMan = CatalogManager.GetInstance();
            Dictionary<int, Account> finalReport = new Dictionary<int, Account>();
            specialAccounts = new List<Account>();
            foreach (AccountBalanceReport report in reports)
            {
                Account reportedAccount = copyAccount(catMan.GetAccount(report.AccountId));
                if (currencyId == MoneyManager.GetInstance().LocalCurrency.Id)
                {
                    reportedAccount.LocalAmount = report.AccountBalance;
                    reportedAccount.SysAmount = reportedAccount.LocalAmount / MoneyManager.GetInstance().SystemCurrency.ToLocalCurrency;
                }
                else
                {
                    reportedAccount.SysAmount = report.AccountBalance;
                    reportedAccount.LocalAmount = reportedAccount.SysAmount * MoneyManager.GetInstance().SystemCurrency.ToLocalCurrency;
                }
                reportedAccount.AlDebe = report.AlDebe;
                if (reportedAccount.SpecialType != Constants.SpecialType.normal)
                    specialAccounts.Add(reportedAccount);
                else
                    addParents(finalReport, reportedAccount);
            }
            return reportListToArray(finalReport.Values);
        }

        /// <summary>
        /// Construye un reporte con base en los balances que recibe de parámetro
        /// </summary>
        /// <param name="reports">Balances de las cuentas que pertenecen al reporte</param>
        /// <param name="currencyId">Moneda en que se está construyendo el reporte</param>
        /// <returns>Lista de cuentas que conforman el reporte</returns>
        private Account[] buildReport(List<AccountBalanceReport> reports, int currencyId)
        {
            CatalogManager catMan = CatalogManager.GetInstance();
            Dictionary<int, Account> finalReport = new Dictionary<int, Account>();
            foreach (AccountBalanceReport report in reports)
            {
                Account reportedAccount = copyAccount(catMan.GetAccount(report.AccountId));
                if (currencyId == MoneyManager.GetInstance().LocalCurrency.Id)
                {
                    reportedAccount.LocalAmount = report.AccountBalance;
                    reportedAccount.SysAmount = reportedAccount.LocalAmount / MoneyManager.GetInstance().SystemCurrency.ToLocalCurrency;
                }
                else
                {
                    reportedAccount.SysAmount = report.AccountBalance;
                    reportedAccount.LocalAmount = reportedAccount.SysAmount * MoneyManager.GetInstance().SystemCurrency.ToLocalCurrency;
                }
                reportedAccount.AlDebe = report.AlDebe;
                addParents(finalReport, reportedAccount);
            }
            return reportListToArray(finalReport.Values);
        }
        
        /// <summary>
        /// Convierte una lista de cuentas en un arreglo de cuentas
        /// </summary>
        /// <param name="list">Lista de cuentas</param>
        /// <returns>Lista hecha un arreglo</returns>
        private Account[] reportListToArray(Dictionary<int, Account>.ValueCollection list)
        {
            Account[] reports = new Account[list.Count];
            list.CopyTo(reports, 0);
            return reports;
        }

        /// <summary>
        /// Crea el reporte del estado de resultados con los balances brindados para la moneda dada
        /// </summary>
        /// <param name="balances">Balances con los que se forma el estado de resultados</param>
        /// <param name="currencyId">Id de la moneda del reporte. Solo local o del sistema</param>
        /// <returns>Devuelve árbol de cuentas, que representan el reporte del estado de resultados</returns>
        private Account[] filterIncomeStatement(List<AccountBalanceReport> balances, int currencyId, out List<Account> specialAccounts)
        {
            List<Constants.AccountType> allowedTypes = new List<Constants.AccountType>();
            allowedTypes.Add(Constants.AccountType.income);
            allowedTypes.Add(Constants.AccountType.outcome);
            allowedTypes.Add(Constants.AccountType.ooutcome);
            allowedTypes.Add(Constants.AccountType.oincome);
            CatalogManager catMan = CatalogManager.GetInstance();
            List<AccountBalanceReport> filteredList = new List<AccountBalanceReport>();
            foreach (AccountBalanceReport balance in balances)
            {
                Account acc =catMan.GetAccount(balance.AccountId);
                if (allowedTypes.Contains(acc.AccountType) || acc.SpecialType == Constants.SpecialType.retainedEarnings)
                    filteredList.Add(balance);
            }
            return buildIncomeReport(filteredList, currencyId, out specialAccounts);
        }

        /// <summary>
        /// Crea el reporte del balance general con los balances brindados para la moneda dada
        /// </summary>
        /// <param name="balances">Balances con los que se forma el balance general</param>
        /// <param name="currencyId">Id de la moneda del reporte. Solo local o del sistema</param>
        /// <returns>Devuelve árbol de cuentas, que representan el reporte del balance general</returns>
        private Account[] filterBalanceSheet(List<AccountBalanceReport> balances, int currencyId)
        {
            List<Constants.AccountType> allowedTypes = new List<Constants.AccountType>();
            allowedTypes.Add(Constants.AccountType.active);
            allowedTypes.Add(Constants.AccountType.passive);
            allowedTypes.Add(Constants.AccountType.capital);
            CatalogManager catMan = CatalogManager.GetInstance();
            List<AccountBalanceReport> filteredList = new List<AccountBalanceReport>();
            foreach (AccountBalanceReport balance in balances)
                if (allowedTypes.Contains(catMan.GetAccount(balance.AccountId).AccountType) )
                    filteredList.Add(balance);
            return buildReport(filteredList, currencyId);
        }

        #endregion
    }
}
